#include "config.h"
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <algorithm>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>


using namespace std;

#define COMMENT_CHAR '#'

static bool IsSpace(char c)
{
    if (' ' == c || '\t' == c)
        return true;
    return false;
}

static bool IsCommentChar(char c)
{
    switch(c) {
    case COMMENT_CHAR:
        return true;
    default:
        return false;
    }
}

static void Trim(string & str)
{
    if (str.empty()) {
        return;
    }
    int i, start_pos, end_pos;
    for (i = 0; i < str.size(); ++i) {
        if (!IsSpace(str[i])) {
            break;
        }
    }
    if (i == str.size()) { // 全部是空白字符串
        str = "";
        return;
    }
    
    start_pos = i;
    
    for (i = str.size() - 1; i >= 0; --i) {
        if (!IsSpace(str[i])) {
            break;
        }
    }
    end_pos = i;
    
    str = str.substr(start_pos, end_pos - start_pos + 1);
}

//清除最后的换行符
static void CutCrLf(string &str)
{
    if(str.empty()){
        return ;
    }
    size_t loc = str.find('\r');
    if(loc == string::npos){
        loc = str.find('\n');
    }

    if(loc == string::npos){
        return ;
    }
    str = str.substr(0, loc);
}

static bool AnalyseLine(const string & line, string & key, string & value)
{
    if (line.empty())
        return false;
    int start_pos = 0, end_pos = line.size() - 1, pos;
    if ((pos = line.find(COMMENT_CHAR)) != -1) {
        if (0 == pos) {  // 行的第一个字符就是注释字符
            return false;
        }
        end_pos = pos - 1;
    }
    string new_line = line.substr(start_pos, start_pos + 1 - end_pos);  // 预处理，删除注释部分
    
    if ((pos = new_line.find('=')) == -1)
        return false;  // 没有=号
        
    key = new_line.substr(0, pos);
    value = new_line.substr(pos + 1, end_pos + 1- (pos + 1));
    
    Trim(key);
    if (key.empty()) {
        return false;
    }
    Trim(value);
    return true;
}

static bool ReadConfig(const string & filename, map<string, string> & m)
{
    m.clear();
    ifstream infile(filename.c_str());
    if (!infile) {
        cout << "file open error" << endl;
        return false;
    }
    string line, key, value;
    while (getline(infile, line)) {
        if (AnalyseLine(line, key, value)) {
            transform(key.begin(), key.end(), key.begin(), ::tolower);//tolower toupper
            CutCrLf(value);
            m[key] = value;
        }
    }
    
    infile.close();
    return true;
}

static void PrintConfig(const map<string, string> & m)
{
    map<string, string>::const_iterator mite = m.begin();
    for (; mite != m.end(); ++mite) {
        cout << mite->first << "=" << mite->second << endl;
    }
}


static bool StringEndWith(const string &str, const string &trim)
{
    size_t loc = str.find(trim);
    if(loc == string::npos){
        return false;
    }

    string tmp = str.substr(loc);
    if(tmp == trim){
        return true;
    }

    return false;
}

CMake::CMake(const string &fileName)
{
    config_file = fileName;
    config_map.clear();
    c_cpp_file_list.clear();
    inc_dir_list.clear();
    config_ok = ReadConfig(fileName, config_map);
    char *p = getcwd(0, 0);
    if(p){
        pwd = p;
        free(p);
    }
}

CMake::~CMake()
{
    config_map.clear();
    c_cpp_file_list.clear();
    inc_dir_list.clear();
}

void CMake::ListFiles(string path)
{
    if(access(path.c_str(), F_OK)){
        return ;
    }

    DIR *dir;
    struct dirent * ptr;
    string unexpect = config_map["unexpect"];
    string build_dir = config_map["build_dir"];
    string file;
    dir = opendir(path.c_str());
    if(!dir){
        return ;
    }

    while(ptr = readdir(dir)){
        file = ptr->d_name;
        if((file == ".") || (file == "..") || (file == unexpect) || (file == build_dir)){
            //cout << "file : " << file << ", unexpect : " << unexpect << endl;
            continue;
        }

        file = path + "/" + file;
        struct stat st;
        stat(file.c_str(), &st);
        if(S_ISDIR(st.st_mode)){
            ListFiles(file);
        }else if(S_ISREG(st.st_mode)){
            if(StringEndWith(file, ".c")){
                printf(".c file : %s\n", file.c_str());
                c_cpp_file_list.push_back(file);
            }else if(StringEndWith(file, ".cpp")){
                printf(".cpp file : %s\n", file.c_str());
                c_cpp_file_list.push_back(file);
            }if(StringEndWith(file, ".h")){
                printf(".h file : %s, dir : %s\n", file.c_str(), path.c_str());
                //c_cpp_file_list.append(file);
                if(!IsHaveIndDir(path)){
                    //没有找到元素
                    inc_dir_list.push_back(path);
                }
            }
        }
    }

    closedir(dir);
    
}

bool CMake::IsHaveIndDir(const string &path)
{
    list<string>::iterator it = inc_dir_list.begin();

    for(; it != inc_dir_list.end(); it++){
        if(*it == path){
            return true;
        }
    }
    return false;
}

bool CMake::SetCmakeFile()
{
    if(!CheckConfig()){
        cout << "Check Config failed \n";
        return false;
    }
    
    list<string>::iterator it;
    map<string, string>::iterator ic ;
    string context;
    FILE *fp = fopen("CMakeLists.txt", "w+");
    if(!fp){
        cout << "fopen failed \n";
        return false;
    }

    fputs("cmake_minimum_required(VERSION 2.8) \n\n", fp);
    
    context = "project( " + config_map["project_name"] + " )\n\n";
    fputs(context.c_str(), fp);

    context = "include_directories(" + config_map["include_dir"] ;
    for(it = inc_dir_list.begin(); it !=inc_dir_list.end(); it++ ){
        context += (" "  + *it + " ");
    }
    context += ")\n\n";
    fputs(context.c_str(), fp);

    ic = config_map.find("ld_dir");
    if(ic != config_map.end()){
        context = "link_directories(" + config_map["ld_dir"] + ")\n\n";
        fputs(context.c_str(), fp);
    }

    ic = config_map.find("compiler");
    if(ic != config_map.end()){
        context = "set (CMAKE_C_COMPILER \""+ config_map["compiler"] +"gcc\")\n\n";
        fputs(context.c_str(), fp);
        context = "set (CMAKE_CXX_COMPILER \""+ config_map["compiler"] +"g++\")\n\n";
        fputs(context.c_str(), fp);
    }

    ic = config_map.find("strip");
    if((ic != config_map.end()) && (ic->second == "1")){
        context = "set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -s\")\n\n";
        fputs(context.c_str(), fp);
        context = "set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -s\")\n\n";
        fputs(context.c_str(), fp);
    }

    ic = config_map.find("c_flags");
    if(ic != config_map.end()){
        context = "set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} " + ic->second + " \")\n\n";
        fputs(context.c_str(), fp);
    }

    ic = config_map.find("cxx_flags");
    if(ic != config_map.end()){
        context = "set(CMAKE_CXX_FLAGS \"${CMAKE_C_FLAGS} " + ic->second + " \")\n\n";
        fputs(context.c_str(), fp);
    }

    context = "add_executable(" + config_map["project_name"] + " ";
    for(it = c_cpp_file_list.begin(); it != c_cpp_file_list.end(); it ++){
        context += (" \t\t"  + *it + " \n");
    }
    context += ")\n\n\n";
    fputs(context.c_str(), fp);

    ic = config_map.find("ld_lib");
    if(ic != config_map.end()){
        context = "target_link_libraries(" + config_map["project_name"] + " " + config_map["ld_lib"]  + ")\n\n\n";
        fputs(context.c_str(), fp);
    }

    fclose(fp);

    ic = config_map.find("build_dir");
    if(ic != config_map.end()){
        string cmd = "mkdir -p " + ic->second;
        system(cmd.c_str());
    }
    
    return true;
}

bool CMake::CheckConfig()
{
    string str = "";
    str = config_map["project_name"] ;
    if(str.length() <= 0){
        cout << "Need Project Name" << endl;
        return false;
    }
    return true;
}